/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>									   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./BSplineInterpolation.h"

/**************************************************************
* Constructors                                                *
***************************************************************/
BSplineInterpolation::BSplineInterpolation(): PointsSet() {
	mCurveColor = RGB(247, 160, 8);
	mArcLengthParametrization = false;
}
    
/**************************************************************
* Operators                                                   *
***************************************************************/


/**************************************************************
* Other helpful functions                                     *
***************************************************************/
/*virtual*/ cVoid BSplineInterpolation::AddPoint(const Point4D &point) {
	PointsSet::AddPoint(point);
	Update();
}

/*virtual*/ cVoid BSplineInterpolation::ReplacePoint(const Point4D& point, cInt iIndex) {
	PointsSet::ReplacePoint(point, iIndex);
	Update();
}

/*virtual*/ cVoid BSplineInterpolation::RemovePoint(cInt i) {
	PointsSet::RemovePoint(i);
	Update();
}

cVoid BSplineInterpolation::PrepareToRender(Matrix4D& matrix) {
	cFloat d = 0.0f, td;
	Point4D *tab = new Point4D[mNrOfPoints];

	for (cInt i = 0; i < mNrOfPoints; i++) {
		tab[i] = matrix * mPoints[i].mPoint;
	}

	for (cInt i = 1; i < mNrOfPoints; i++) {
		if ((td = (tab[i] - tab[i - 1]).Abs()) > d) {
			d = td;
		}
	}
	mDrawingCurvePrecision = (cInt) (d * 0.05f);
}

/*virtual*/ cVoid BSplineInterpolation::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	
	if (mNrOfPoints > 3) {
		PrepareToRender(matrix);	
		for (cInt i = 0; i < (cInt) mCoeffs.size(); i++) {
			if (mHList[i] == 0.0f) {
				continue;
			}
			Point4D point;
			cInt x, y, px, py;
			cFloat dt = mHList[i] / mDrawingCurvePrecision, t = dt;
			cBool v = true;

			point = matrix * mCoeffs[i].mA;
			px = (cInt) (point.mX / point.mW);
			py = (cInt) (point.mY / point.mW);
			for (cInt j = 0; j < mDrawingCurvePrecision; j++, t += dt) {
				point.mX = mCoeffs[i].mA.mX + t * (mCoeffs[i].mB.mX + t * (mCoeffs[i].mC.mX + mCoeffs[i].mD.mX * t));
				point.mY = mCoeffs[i].mA.mY + t * (mCoeffs[i].mB.mY + t * (mCoeffs[i].mC.mY + mCoeffs[i].mD.mY * t));
				point.mZ = mCoeffs[i].mA.mZ + t * (mCoeffs[i].mB.mZ + t * (mCoeffs[i].mC.mZ + mCoeffs[i].mD.mZ * t));
				point.mW = 1.0f;
				point = matrix * point;

				if (point.mW < plane.GetNearClippingPlane()) {
					v = false;
				} else {
					x = (cInt) (point.mX / point.mW);
					y = (cInt) (point.mY / point.mW);
					if (v) {
						renderer.DrawROPLine(x + iHalfWidth, -y + iHalfHeight, 
							px + iHalfWidth, -py + iHalfHeight, mCurveColor);
					}
					px = x; 
					py = y;
					v = true;
				}
			}
		}
	}
	PointsSet::Render(renderer, matrix, plane);
}

/*virtual*/ cVoid BSplineInterpolation::Render(Renderer& renderer, Matrix4D& matrix, ProjectionPlane& plane, cWinColor color) {
	cInt iHalfWidth = renderer.GetWidth() >> 1, iHalfHeight = renderer.GetHeight() >> 1;
	
	if (mNrOfPoints > 3) {
		PrepareToRender(matrix);
		for (cInt i = 0; i < (cInt) mCoeffs.size(); i++) {
			if (mHList[i] == 0.0f) {
				continue;
			}
			Point4D point;
			cInt x, y, px, py;
			cFloat dt = mHList[i] / mDrawingCurvePrecision, t = dt;
			cBool v = true;

			point = matrix * mCoeffs[i].mA;
			px = (cInt) (point.mX / point.mW);
			py = (cInt) (point.mY / point.mW);
			for (cInt j = 0; j < mDrawingCurvePrecision; j++, t += dt) {
				point.mX = mCoeffs[i].mA.mX + t * (mCoeffs[i].mB.mX + t * (mCoeffs[i].mC.mX + mCoeffs[i].mD.mX * t));
				point.mY = mCoeffs[i].mA.mY + t * (mCoeffs[i].mB.mY + t * (mCoeffs[i].mC.mY + mCoeffs[i].mD.mY * t));
				point.mZ = mCoeffs[i].mA.mZ + t * (mCoeffs[i].mB.mZ + t * (mCoeffs[i].mC.mZ + mCoeffs[i].mD.mZ * t));
				point.mW = 1.0f;
				point = matrix * point;

				if (point.mW < plane.GetNearClippingPlane()) {
					v = false;
				} else {
					x = (cInt) (point.mX / point.mW);
					y = (cInt) (point.mY / point.mW);
					if (v) {
						renderer.DrawROPLine(x + iHalfWidth, -y + iHalfHeight, 
							px + iHalfWidth, -py + iHalfHeight, color);
					}
					px = x; 
					py = y;
					v = true;
				}
			}
		}
	}
	PointsSet::Render(renderer, matrix, plane, color);
}

cVoid BSplineInterpolation::Update() {	
	if (mNrOfPoints < 4) {
		return;
	}
	CalculateH();
	Interpolate();
}

cVoid BSplineInterpolation::Interpolate() {
	mCoeffs.clear();

	cFloat *a = new cFloat[mNrOfPoints];	// under diagonal
	cFloat *b = new cFloat[mNrOfPoints];	// diagonal
	cFloat *c = new cFloat[mNrOfPoints];	// over diagonal
	Point4D *y = new Point4D[mNrOfPoints];

	// calculating tridiagonal matrix
	for (cInt i = 1; i < mNrOfPoints - 1; i++) {
		if (mHList[i - 1] == 0 && mHList[i] == 0) {
			c[i - 1] = b[i - 1] = 0.0f;
		} else {
			b[i - 1] = mHList[i - 1] / (mHList[i - 1] + mHList[i]);
			c[i - 1] = mHList[i] / (mHList[i - 1] + mHList[i]);
		}
		a[i - 1] = 2.0f;
	}

	// preparing input data points D
	for (cInt i = 0; i < mNrOfPoints - 2; i++) {
		Point4D w1, w2;
		if (mHList[i + 1] == 0.0f) {
			w1 = Point4D();
		} else {
			w1 = (mPoints[i + 2].mPoint - mPoints[i + 1].mPoint) / mHList[i + 1];
		}
		if (mHList[i] == 0.0f) {
			w2 = Point4D();
		} else {
			w2 = (mPoints[i + 1].mPoint - mPoints[i].mPoint) / mHList[i];
		}
		if (mHList[i] + mHList[i + 1] == 0.0f) {
			y[i] = Point4D();
		} else {
			y[i] = (w1 - w2) * 3.0f / (mHList[i] + mHList[i + 1]);
		}
	}
	CalculateCoefficients(a, b, c, y);

	delete []a;
	delete []b;
	delete []c;
	delete []y;
}

cVoid BSplineInterpolation::CalculateCoefficients(cFloat* a, cFloat* b, cFloat* c, Point4D* y) {
	cFloat *alfa = new cFloat[mNrOfPoints];
	cFloat *beta = new cFloat[mNrOfPoints];
	Point4D *X = new Point4D[mNrOfPoints];
	Point4D *P = new Point4D[mNrOfPoints];

	alfa[0] = a[0];
	for (cInt i = 1; i < mNrOfPoints - 2; i++) {
		beta[i] = b[i] / alfa[i - 1];
		alfa[i] = a[i] - beta[i] * c[i - 1];
	}
	// calculating X in LX = D
	X[0] = y[0];
	for (cInt i = 1; i < mNrOfPoints - 2; i++) {
		X[i] = y[i] - X[i - 1] * beta[i];
	}
	// calculating P in UP = X
	P[mNrOfPoints - 2] = X[mNrOfPoints - 3] / alfa[mNrOfPoints - 3];
	for (cInt i = mNrOfPoints - 4; i >= 0; i--) {
		P[i + 1] = (X[i] - P[i + 2] * c[i]) / alfa[i];
	}
	P[0] = Point4D();
	P[mNrOfPoints - 1] = Point4D();

	// writing B-Spline
	for (cInt i = 0; i < mNrOfPoints - 1; i++) {
		BSplineCoefficient coeff;
		coeff.mA = mPoints[i].mPoint;
		coeff.mB = (mPoints[i + 1].mPoint - mPoints[i].mPoint) / mHList[i] -
						(P[i + 1] + P[i] * 2.0f) * mHList[i] / 3.0f;
		coeff.mC = P[i];
		coeff.mD = (P[i + 1] - P[i]) / (3.0f * mHList[i]);
		mCoeffs.push_back(coeff);
	}
	delete []alfa;
	delete []beta;
	delete []X;
	delete []P;
}

cVoid BSplineInterpolation::CalculateH() {
	mHList.clear();
	for (cInt i = 0; i < mNrOfPoints - 1; i++) {
		if (mArcLengthParametrization) {
			mHList.push_back((mPoints[i + 1].mPoint - mPoints[i].mPoint).Abs());
		} else {
			mHList.push_back(1.0f);
		}
	}
}

cVoid BSplineInterpolation::SwitchParametrization() {
	mArcLengthParametrization = !mArcLengthParametrization;
	Update();
}

/*virtual*/ cVoid BSplineInterpolation::DisplayInfo(Renderer& renderer) {
	cChar pcBuff [MAX_BUF_SIZE];
	cInt iLineNr = 0;

	sprintf_s(pcBuff, "B-Spline Interpolation Info:");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, mCurveColor);
	sprintf_s(pcBuff, "Nr of points :");	
	renderer.DrawString(H_TXT_SPACER, iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "%3i", mNrOfPoints);	
	renderer.DrawString(H_TXT_SPACER * 13, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "Knots Parametrization Type: ", mNrOfPoints);	
	renderer.DrawString(H_TXT_SPACER, iLineNr * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, mArcLengthParametrization ? "Arc Length" : "Uniform", mNrOfPoints);	
	renderer.DrawString(H_TXT_SPACER * 30, iLineNr++ * V_TXT_SPACER, pcBuff, 
		true, true, true, Color::ToWinColor(WHITE));
	iLineNr++;
	iLineNr++;
	sprintf_s(pcBuff, "[Ctrl + P] - Change Parametrization");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "[Ctrl + V] - Show/Hide Points Coordinates");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "Space - Deselect Points");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "LMB - Select/Deselect Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "RMB - Add Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "DEL - Delete Selected Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
	sprintf_s(pcBuff, "LMB & Drag - Move Selected Point");
	renderer.DrawString(H_TXT_SPACER, iLineNr++ * V_TXT_SPACER, pcBuff, 
		false, true, true, Color::ToWinColor(WHITE));
}

/*virtual*/ cChar* BSplineInterpolation::GetName() {
	return "BSplineInterpolation";
}